查看原文
其他

Android上下文Context,学有所得

新小梦 鸿洋
2024-08-24

本文作者


作者:新小梦

链接:

https://juejin.cn/post/7218080719201321021

本文由作者授权发布。


1Context概览


Context在Android中,代表着当前应用程序运行环境的上下文,通过前面对Android应用程序的启动流程和四大组件的启动流程分析。发现只有Application、Activity、Service三者才会创建上下文Context。所以,我们也可以理解为Context是Application、Activity、Service运行环境的上下文。所谓上下文,即可以根据一些变量、类来帮助我们获得相关资源、信息。类似聊天中,我们有时候需要前面沟通的信息,来理解当前对话的内容。从Android Studio继承窗口,我们找出Context的子类。
再根据前面分析启动流程中,ContextWrapper内部有一个指向Context类型的属性mBase,再实际运行会被赋值为ContextImpl实例。根据这些,我们画出主要的类图信息。
Context本身是一个抽象类,定义了通用的接口,这样我们在使用的时候,不会感知具体调用了哪些子类。即面向抽象,而不是具体。而Context的两个直接子类ContextWrapper和ContextImpl,我们在研究Applicaton、Activity、Service的启动流程,都会发现创建一个ContextImpl实例,并和三者绑定,也就是实例设置给ContextWrapper的mBase属性。而我们调用Context的一些能力,基本都会在ContextWrapper通过mBase转给ContextImpl对象去实现。ContextWrapper在这里理解为装饰者。而不需要主题相关的Application和Service直接继承ContextWrapper,需要主题相关的Activity再经一层ContextThemeWrapper修饰。

本文分析的内容,可能和之前流程分析有些重复,但每篇文章的主题侧重点不用,分析的内容也不尽相同。


2Application上下文


Application与Context的创建和关联

伴随着应用程序进程的启动,在执行主线程ActivityThread主函数main的时候,会执行ActivityThread的attach函数。
//ActivityThrad#main
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);


1、ActivityThread.attach

通过ContextImpl的静态函数createAppContext创建ContextImpl实例context,然后通过context去创建Application实例mInitialApplication,并回调mInitialApplicationonCreate生命周期函数。
private void attach(boolean system, long startSeq) {
    ...
    ContextImpl context = ContextImpl.createAppContext(
            this, getSystemContext().mPackageInfo);
    mInitialApplication = context.mPackageInfo.makeApplication(truenull);
    mInitialApplication.onCreate();
    ...
}

ContextImpl.createAppContext
createAppContext有两个重载函数。第2个重载函数直接new了一个ContextImpl实例。
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    return createAppContext(mainThread, packageInfo, null);
}

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
        String opPackageName) 
{
    ...
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, nullnullnullnull,0null, opPackageName);
    context.setResources(packageInfo.getResources());
    context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
    return context;
}


关注一下ContextImpl的构造函数,然后ContextImpl的实例拥有应用程序相关资源的引用,成为调用其他资源的入口。
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
        @NonNull LoadedApk packageInfo, @Nullable String attributionTag,
        @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user,
        int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {
    //未与Application,或者是Activity、Service绑定,将mOuterContext先指向自己,
    mOuterContext = this;
    ...
    //接下来,将应用程序运行的相关信息,引用赋值给ContextImpl的属性
    //这样子,通过Context这个入口,就可以访问到相关资源
    mMainThread = mainThread;
    mToken = activityToken;
    mFlags = flags;

    if (user == null) {
        user = Process.myUserHandle();
    }
    mUser = user;
    //LoadedApk赋值
    mPackageInfo = packageInfo;
    mSplitName = splitName;
    mClassLoader = classLoader;
    mResourcesManager = ResourcesManager.getInstance();

    String opPackageName;

    if (container != null) {
        mBasePackageName = container.mBasePackageName;
        opPackageName = container.mOpPackageName;
        setResources(container.mResources);
        mDisplay = container.mDisplay;
        mIsAssociatedWithDisplay = container.mIsAssociatedWithDisplay;
        mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext;
    } else {
        mBasePackageName = packageInfo.mPackageName;
        ApplicationInfo ainfo = packageInfo.getApplicationInfo();
        if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {
            opPackageName = ActivityThread.currentPackageName();
        } else {
            opPackageName = mBasePackageName;
        }
    }

    mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName;
    mAttributionTag = attributionTag;
    //前面研究ContentProvider启动流程,mContentResolver初始化的地方
    mContentResolver = new ApplicationContentResolver(this, mainThread);
}


2、LoadedApk.makeApplication

回到attach函数,调用了context的LoadedApk类型的mPackageInfo属性makeApplication函数。其中mPackageInfo在ContextImpl的构造函数中被赋值。说实话,我确实不知道这样为啥要先创建ContextImpl实例context,然后再通过context去调LoadedApk实例的makeApplication函数。
分析一:创建Application的上下文ContextImpl类型的appContext。
分析二:创建Application实例app和app绑定appContext。
分析三:appContext绑定app。
//LoadedApk#makeApplication xxm
public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }

    Application app = null;

    String appClass = mApplicationInfo.className;
    //没有自定义Application,就使用默认的
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }

    ...

    final java.lang.ClassLoader cl = getClassLoader();

    ...
    //这里才是真正创建Application上下文的地方,所以上面创建Context实例的作用是?
    //分析一
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

    ...
    //分析二
    app = mActivityThread.mInstrumentation.newApplication(
            cl, appClass, appContext);
    //分析三
    appContext.setOuterContext(app);
    ...
    mActivityThread.mAllApplications.add(app);
    //通过AMS创建返回的赋值给mApplication
    mApplication = app;

    if (instrumentation != null) {
        ...
        instrumentation.callApplicationOnCreate(app);
        ...
    }
    return app;
}


从makeApplication开始,贴一下时序图。
分析一,创建ContextImpl的实例可以参考第1小节。

3、Instrumentation.newApplication

在分析二中调用了InstrumentationnewApplication函数创建了Application的实例app,并关联上下文context。
public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException 
{
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}


getFactory函数返回的是AppComponentFactory类型的实例,其instantiateApplication函数,通过反射创建Application实例。


public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
        @NonNull String className)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    return (Application) cl.loadClass(className).newInstance();
}


而在创建的Application类型的app之后,newApplication函数调用的app的attach绑定上下文context。因为Application本身继承自ContentWrapper,所以自身也是个上下文。
Application的attach函数的主要作用就是将makeApplication函数创建的ContextImpl实例appContext一路传递进来,设置给Application的父类ContextWrapper的mBase属性。也就是说,现在Application关联到了ContextImpl类型的上下文mBase。后续我们通过Application调用Context相关的能力,就会转移到了ContextImpl的实例mBase。
final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}


4、ContextImpl.setOuterContext

makeApplication函数分析三中调用了appContext的setOuterContext函数。ContextImpl类有一个Context类型的mOuterContext属性。在调用ContextImpl构造函数的时候,该属性指向了自己。而setOuterContext函数将该属性指向Application实例。
final void setOuterContext(Context context) {
    mOuterContext = context;
}


到这里,Application和ContextImpl就互相关联。

ApplicationContext的使用

我们在ContextWrapper的子类Application、Activity、Service都可以通过getApplicationContext函数来获取Application的上下文。
public Context getApplicationContext() {
    return mBase.getApplicationContext();
}


通过上面的分析,我们知道mBase的实际类型是ContextImpl。
public Context getApplicationContext() {
    return (mPackageInfo != null) ?
            mPackageInfo.getApplication() : mMainThread.getApplication();
}


这里的LoadedApk类型的mPackageInfo肯定不为null,调用了LoadedApk实例的getApplication函数。
Application getApplication() {
    return mApplication;
}


mApplication正是前面调用LoadedApk.makeApplication函数,创建的Applicaiton实例,Application本身也继承Context。所以我们通过getApplicationContext获得了应用的上下文,调用相关Context的能力都会转给Application持有的实际是ContextImpl类型的mBase对象。


3Activity的上下文


在研究Activity的启动流程过程,最终会走到ActivityThread的performLaunchActivity函数。

https://juejin.cn/post/7211800391012565048


private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ...
    //分析一:创建ContextImpl实例
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    ...
    java.lang.ClassLoader cl = appContext.getClassLoader();
    //分析二:创建Activity实例
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);

    ...

    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    //分析三:appContext关联Activity
    appContext.setOuterContext(activity);
    //分析四:activity关联appContext
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config,
            r.referrer, r.voiceInteractor, window, r.configCallback,
            r.assistToken);

    ...
    return activity;
}

Activity上下文的创建和Application上下文的创建是类似的。分析一通过createBaseContextForActivity创建ContextImpl的实例appContext;分析二创建Activity的实例activity,这部分可以参考Activity的启动流程最后部分。分析三:将activity设置给appContext,这里appContext的mBase就指向了activity。分析四调用activity的attach方法,关联appContext。

ActivityThread.createBaseContextForActivity

分析一:通过createBaseContextForActivity函数为当前Activity创建一个ContextImpl类型的上下文实例appContext。调用了createActivityContext函数。
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    ...
    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
    ...
    return appContext;
}

ContextImpl.createActivityContext

createActivityContext函数同样是通过ContextImpl构造函数创建ContextImpl实例,然后多设置了更多属性。
static ContextImpl createActivityContext(ActivityThread mainThread,LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) {

    ...

    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
            activityInfo.splitName, activityToken, null0, classLoader, null);
    context.mIsUiContext = true;
    context.mIsAssociatedWithDisplay = true;
    context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);

    displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;

    final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
            ? packageInfo.getCompatibilityInfo()
            : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

    final ResourcesManager resourcesManager = ResourcesManager.getInstance();

    context.setResources(resourcesManager.createBaseTokenResources(activityToken,
            packageInfo.getResDir(),
            splitDirs,
            packageInfo.getOverlayDirs(),
            packageInfo.getApplicationInfo().sharedLibraryFiles,
            displayId,
            overrideConfiguration,
            compatInfo,
            classLoader,
            packageInfo.getApplication() == null ? null
                    : packageInfo.getApplication().getResources().getLoaders()));
    context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
            context.getResources());
    return context;
}


Activity.attach


分析四:在创建ContextImpl实例appContext和Activity实例activity后,将activity关联到appContext。然后调用activity的attach函数,将appContext关联到activity中。
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    ...
    attachBaseContext(context);
    ...
}

调用了attachBaseContext函数。
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
}
//ContextThemeWrapper xxm
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(newBase);
    ...
}
//ContextWrapper xxm
protected void attachBaseContext(Context base) {
    ...
    mBase = base;
}

appContext经过一路传递最终还是设置给了Activity的父类ContextWrapper的mBase变量。

到这里Activity和Context就相关关联了。后续在Activity关于Context的相关操作都会传递到实际类型是ContextImpl的mBase中去 。


4Service的上下文

Service的上下文,其实在Service的启动流程已经分析过了,为了Context的完整性,摘抄过来。

https://juejin.cn/post/7208005892302176317


在Service的启动流程的最后,调用了ActivityThread的handleCreateService函数。看完handleCreateService函数之后,其实已经不用再进一步仔细分析了,和Application与Activity的上下文太惊人的相似。
private void handleCreateService(CreateServiceData data) {
    ...
    Service service = null;
        //分析一
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);

        Application app = packageInfo.makeApplication(false, mInstrumentation);
        java.lang.ClassLoader cl = packageInfo.getClassLoader();

        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
        ...
        //分析二
        context.setOuterContext(service);
        //分析三
        service.attach(context, thisdata.info.name, data.token, app,
                ActivityManager.getService());
        ...

}

分析一:通过ContextImpl的静态函数createAppContext返回了一个ContextImpl类型的context。createAppContext又调用了重载函数createAppContext。直接新建了ContextImpl实例context,构造函数传递了ActivityThread类型的mainThread和LoadedApk类型的packageInfo。并给context设置了资源环境和是否Syetem属性。
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    return createAppContext(mainThread, packageInfo, null);
}

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
        String opPackageName) 
{
    if (packageInfo == nullthrow new IllegalArgumentException("packageInfo");
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, nullnullnullnull,
            0null, opPackageName);
    context.setResources(packageInfo.getResources());
    context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
    return context;
}

回到handleCreateService函数的分析二,在创建好Service对象service之后,将service作为参数传递给了context.setOuterContext函数。Service本身继承自ContextWrapper,ContextWrapper又是Context的子类。这时候的setOuterContext函数将service设置给了context的mOuterContext属性。意味着当前上下文context持有当前新建的service引用。
在分析三,调用了service.attach函数,context并作为第一个参数被传入。attach函数又调用了attachBaseContext函数。后面的分析就省略了。
public final void attach(
        Context context,
        ActivityThread thread, String className, IBinder token,
        Application application, Object activityManager) 
{
    attachBaseContext(context);
    ...
}

5总结

之所以能称为上下文,是因为Context的实现类ContextImpl持有应用程序运行过程相关资源类应用,Context作为一个入口,通过Application、Activity、Service的修饰下,更加方便的访问这些资源。


最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

Android就想下载个文件到SD卡,怎就这么难?快把代码拿走吧
Google CameraX,看这篇文章就够了
Android  zygote访谈录



扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

继续滑动看下一个
鸿洋
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存